Le football est très sûrement l’un des sports dans lequel la data est le plus implanté. Il est maintenant possible de trouver des données pour chaque type d’action et fait de jeu. Une part importante de ces données est accessible au grand public par l’intermédiaire d’api ou de packages. Dans ce projet, nous allons utiliser le package R worldfootballR pour visualiser et analyser les tirs des équipes dans les cinq grands championnats européens. Dans une première partie, nous allons visualiser la position des tirs des équipes à l’aide de heat maps. Ensuite, nous analyserons le ratio tirs/buts marqués dans les cinq championnats étudiés.
Dans cette étude, nous nous basons sur les données des cinq championnats européens depuis le début de cette saison (2023-24) au 30/11/2023.
Dans cette partie, nous allons tenter de créer une visualisation des tirs des équipes sous la forme d’heat map. Ce type de visualisation est très régulièrement utilisé car facile à comprendre et à réaliser. Ce travail va également nous permettre de démontrer la facilité de création d’heat map dès lors que nous en sommes en possession de données.
L’importation des données se fait à l’aide de la fonction
understat_league_season_shots. Les données sont récupérées
sur le site Understat.
#bundesliga <- understat_league_season_shots("Bundesliga", 2023)
#ligue1 <- understat_league_season_shots("Ligue 1", 2023)
#Liga <- understat_league_season_shots("La liga", 2023)
#Premier_League <- understat_league_season_shots("EPL", 2023)
#Serie_A <- understat_league_season_shots("Serie A", 2023)
bundesliga <- read.csv("data/bundesliga.csv")
ligue1 <- read.csv("data/ligue1.csv")
Liga <- read.csv("data/Liga.csv")
Premier_League <- read.csv("data/Premier_League.csv")
Serie_A <- read.csv("data/Serie_A.csv")
Une fois les données importées, nous les transformons en objet
tibble pour pouvoir les manipuler plus facilement.
bundesliga <- as_tibble(bundesliga)
ligue1 <- as_tibble(ligue1)
Liga <- as_tibble(Liga)
Premier_League <- as_tibble(Premier_League)
Serie_A <- as_tibble(Serie_A)
Voici à quoi ressemble un de nos cinq jeux données (Chaque jeu de données possède la même structure) :
head(bundesliga)
## # A tibble: 6 Ă— 21
## league id minute result X Y xG player h_a player_id
## <chr> <int> <int> <chr> <dbl> <dbl> <dbl> <chr> <chr> <int>
## 1 Bundesliga 532854 22 SavedShot 0.741 0.569 0.0594 Marvi… h 4329
## 2 Bundesliga 532862 45 MissedShots 0.84 0.48 0.0255 Mitch… h 28
## 3 Bundesliga 532864 46 MissedShots 0.92 0.45 0.422 Leona… h 262
## 4 Bundesliga 532865 48 MissedShots 0.893 0.557 0.0880 Nicla… h 6098
## 5 Bundesliga 532870 62 MissedShots 0.781 0.503 0.0322 Jens … h 10734
## 6 Bundesliga 532879 91 MissedShots 0.759 0.567 0.0130 Roman… h 9069
## # ℹ 11 more variables: situation <chr>, season <int>, shotType <chr>,
## # match_id <int>, home_team <chr>, away_team <chr>, home_goals <int>,
## # away_goals <int>, date <chr>, player_assisted <chr>, lastAction <chr>
Voici les situations de jeux qui sont prises en compte dans nos données :
unique(bundesliga$situation)
## [1] "DirectFreekick" "OpenPlay" "SetPiece" "FromCorner"
## [5] "Penalty"
Nous sauvegardons les noms des équipes dans des vecteurs pour faciliter nos visualisations par la suite.
bundesliga_teams <- unique(bundesliga$home_team)
ligue1_teams <- unique(ligue1$home_team)
Liga_teams <- unique(Liga$home_team)
Premier_League_teams <- unique(Premier_League$home_team)
Serie_A_teams <- unique(Serie_A$home_team)
bundesliga_teams
## [1] "Werder Bremen" "Bayer Leverkusen" "Wolfsburg"
## [4] "Hoffenheim" "Augsburg" "VfB Stuttgart"
## [7] "Borussia Dortmund" "Union Berlin" "Eintracht Frankfurt"
## [10] "RasenBallsport Leipzig" "Freiburg" "FC Cologne"
## [13] "Bochum" "FC Heidenheim" "Darmstadt"
## [16] "Borussia M.Gladbach" "Mainz 05" "Bayern Munich"
Pour créer la fonction de création de heat map, deux éléments sont importants :
annotate_pitchet theme_pitch.Nous allons également représenter les buts sur la heat map à l’aide de points noirs.
# Colors for the heat map
custom_palette <- c("transparent", "green", "yellow", "orange", "red")
# Heat map function
heat_map_shots <- function(team, league, df) {
data_home <- filter(df, df$home_team == team & df$h_a=="h")
data_away <- filter(df, df$away_team == team & df$h_a=="a")
data <- bind_rows(data_home, data_away)
logo <- readPNG(sprintf("logos/%s/%s.png",league,team))
p <- ggplot(data, aes(x=X*100, y=Y*100) ) +
annotate_pitch(colour = "white",
fill = "springgreen4",
limits = FALSE,
linewidth = 1) +
theme_pitch() +
theme(panel.background = element_rect(fill = "springgreen4")) +
stat_density_2d(aes(fill = ..density..), geom = "raster", contour = FALSE) +
scale_fill_gradientn(colors = custom_palette, guide = "none") +
geom_point(data =filter(data, result=="Goal"), aes(X*100, Y*100), color="black")+
coord_flip(xlim = c(49, 101)) +
ggtitle(team)+
theme(
plot.title = element_text(hjust = 0.5, size = 20, face = "bold")
)+
annotation_custom(rasterGrob(logo, width = unit(1, "npc"), height = unit(1, "npc")),
xmin= 55,xmax = 65, ymin = 85, ymax = 95)
return(p)
}
Voici Ă quoi ressemble notre heat map :
heat_map_shots("Bayern Munich", "bundesliga", bundesliga)
Sur cette heat map, la variation de couleur représente la densité des tirs pris par l’équipe et les points noirs, les buts inscrits par l’équipe.
Maintenant, nous allons créer une représentation globale pour chaque championnat où nous pourrons visualiser la heat map de chaque club.
Nous créons deux représentations qui servirons de légende pour notre représenation globale.
# Legend goals
data_legend_goal <- data.frame(
x = c(1),
y = c(1)
)
legend_goal <- ggplot(data_legend_goal, aes(x=x,y=y))+
geom_point(aes(size=c(7))) +
ylim(-1.2,1.2) +
xlim(-4,6) +
theme_minimal() +
ggtitle("Goal")+
theme(
panel.grid = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text = element_blank(),
plot.title = element_text(hjust = 0.5, size = 20, face = "bold"),
legend.position = "none"
)
# Legend shots
df <- data.frame(value = c(75),
group = c(1))
df_expanded <- df %>%
rowwise() %>%
summarise(group = group,
value = list(0:value)) %>%
unnest(cols = value)
legend_shots <- df_expanded %>%
ggplot() +
geom_tile(aes(
x = group,
y = value,
fill = value,
width = 0.9
)) +
coord_flip() +
scale_fill_gradientn(colors = custom_palette, guide = "none") +
theme(legend.position = "none") +
xlim(0,2) +
theme_minimal() +
ggtitle("Shots density")+
theme(
panel.grid = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text = element_blank(),
plot.title = element_text(hjust = 0.5, size = 20, face = "bold")
)
Pour chaque championnat, on créé une liste avec la heat map de chaque équipe à laquelle on ajoute les deux représentations de légende.
# Bundesliga
plots_bundesliga <- lapply(bundesliga_teams, function(i) {
heat_map_shots(i, "bundesliga", bundesliga)
})
plots_bundesliga <- c(list(legend_goal, legend_shots), plots_bundesliga)
# Ligue 1
plots_ligue1 <- lapply(ligue1_teams, function(i) {
heat_map_shots(i, "Ligue_1", ligue1)
})
plots_ligue1 <- c(list(legend_goal, legend_shots), plots_ligue1)
# Premier League
plots_Premier_League <- lapply(Premier_League_teams, function(i) {
heat_map_shots(i, "Premier League", Premier_League)
})
plots_Premier_League <- c(list(legend_goal, legend_shots), plots_Premier_League)
# La Liga
plots_Liga <- lapply(Liga_teams, function(i) {
heat_map_shots(i, "liga", Liga)
})
plots_Liga <- c(list(legend_goal, legend_shots), plots_Liga)
# Serie A
plots_Serie_A <- lapply(Serie_A_teams, function(i) {
heat_map_shots(i, "Serie A", Serie_A)
})
plots_Serie_A <- c(list(legend_goal, legend_shots), plots_Serie_A)
Voici les représentations globales des heat maps de chaque équipe pour nos cinq championnats.
Ces représentations sont très intéressantes, car elles nous permettent d’avoir un aperçu de la manière dont chaque équipe attaque le but. Certaines équipes vont tirer très proche du but comme l’Atletico Madrid, d’autres vont diversifier leurs zones de tir et vont prendre beaucoup de leurs tirs en dehors de la surface comme Lecce. Ces représentations nous permettent aussi de voir que des équipes ne marquent quasiment que d’un seul côté comme Liverpool (gauche), ou au contraire, dans plusieurs zones dans et en dehors de la surface comme Naples.
Dans cette partie, nous allons observer l’efficacité de chaque équipe face au but en visualisant leur ratio tirs/buts inscrits.
La fonction renvoie un dataframe avec le nombre de tirs et de buts de chaque équipe d’un championnat.
df_sg <- function(df, teams, league){
shots <- c()
goals <- c()
LogoPath <- c()
for(team in teams){
data_home <- filter(df, df$home_team == team & df$h_a=="h")
data_away <- filter(df, df$away_team == team & df$h_a=="a")
data_team <- bind_rows(data_home, data_away)
shots <- c(shots, nrow(data_team))
goals <- c(goals, nrow(filter(data_team, result=="Goal")))
LogoPath <- c(LogoPath, sprintf("logos/%s/%s.png",league, team))
}
sg <- tibble(team = teams, shots, goals, logos = LogoPath)
sg <-column_to_rownames(sg, var="team")
return(sg)
}
df_sg_bundesliga <- df_sg(bundesliga, bundesliga_teams, "bundesliga")
df_sg_ligue_1 <- df_sg(ligue1, ligue1_teams, "Ligue_1")
df_sg_premier_league <- df_sg(Premier_League, Premier_League_teams, "Premier League")
df_sg_liga <- df_sg(Liga, Liga_teams, "liga")
df_sg_serie_a <- df_sg(Serie_A, Serie_A_teams, "Serie A")
head(df_sg_ligue_1)
## shots goals logos
## Marseille 170 12 logos/Ligue_1/Marseille.png
## Nice 171 13 logos/Ligue_1/Nice.png
## Brest 182 13 logos/Ligue_1/Brest.png
## Paris Saint Germain 216 34 logos/Ligue_1/Paris Saint Germain.png
## Nantes 166 17 logos/Ligue_1/Nantes.png
## Clermont Foot 156 8 logos/Ligue_1/Clermont Foot.png
plot_sg <- function(df, league, size=.1){
ggplot(df, aes(shots, goals)) +
geom_smooth(method=lm, color="red", fill="blue", se=TRUE) +
geom_image(aes(image=logos), size=size) +
ggtitle(sprintf("Shots / Goals comparison %s", league)) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5, size = 20, face = "bold"),
axis.title.x = element_text(hjust = 0.5, size = 15),
axis.title.y = element_text(hjust = 0.5, size = 15),
axis.line = element_line(colour = "black"),
axis.text.x = element_text(face = "bold"),
axis.text.y = element_text(face = "bold")
)
}
Ces représentations sont très intéressantes et nous montrent déjà très logiquement que plus on prend de tirs, plus on marque. Cependant, ce n’est pas le cas de toutes les équipes. On remarque qu’en Ligue 1, Clermont et Lyon manquent de réalisme en étant dans la moyenne en termes de tirs, mais sont les deux pires équipes en ce qui concerne les buts marqués. En Premier League, Newcastle fait preuve d’un réalisme exceptionnel en étant la dixième équipe tirant le plus au but, mais la deuxième équipe ayant inscrit le plus de buts. Ce phénomène s’applique aussi en Liga avec l’Atletico Madrid et Girone. Ces deux équipes, qui sont dans le trio de tête du championnat espagnol prennent quasiment autant de tirs que le 18e du championnat, le Celta Vigo. En Allemagne et en Italie, le ratio tirs/buts et assez similaires pour presque toutes les équipes.
df_sg_all <- bind_rows(df_sg_bundesliga, df_sg_liga, df_sg_ligue_1, df_sg_premier_league, df_sg_serie_a)
À présent, lorsqu’on compare le ratio tirs/buts entre toutes les équipes des cinq championnats européens, on observe que le Bayer Leverkusen, leader de Bundesliga est l’une des équipes européennes avec le plus de réalisme. Le fait que nous n’ayons pas remarqué cette équipe dans nos premières représentations peut témoigner du réalisme face au but des équipes allemandes.
Enfin, nous pouvons réaliser un classement du réalisme face au but. Pour cela, nous allons classer les équipes en fonction de leur nombre de buts divisé par le nombre de tirs.
df_sg_all["ratio"] = df_sg_all$goals/df_sg_all$shots
Voici les 10 équipes les plus réalistes face au but.
head(arrange(df_sg_all, desc(ratio))[c("ratio")],10)
## ratio
## Newcastle United 0.1812865
## Bayer Leverkusen 0.1794872
## Bayern Munich 0.1757322
## Atletico Madrid 0.1734104
## RasenBallsport Leipzig 0.1676301
## Girona 0.1666667
## VfB Stuttgart 0.1623037
## Paris Saint Germain 0.1574074
## Manchester City 0.1534884
## Hoffenheim 0.1509434
Dans ce classement, nous retrouvons en tête une partie des équipes ayant sur-performé qui ont été évoquées précédemment.
Voici les dix équipes les moins réalistes face au but.
head(arrange(df_sg_all, ratio)[c("ratio")],10)
## ratio
## Lyon 0.04790419
## Udinese 0.04907975
## Clermont Foot 0.05128205
## Empoli 0.05755396
## FC Cologne 0.05769231
## Alaves 0.05820106
## Verona 0.06382979
## Bochum 0.06432749
## Celta Vigo 0.06842105
## Sheffield United 0.06896552
Comme avec le haut de classement, nous retrouvons ici une partie des équipes qui ont été visées précédemment pour leur gros manque de réalisme.
Pour conclure, nous avons pu montrer la facilité de création et d’analyses de représentations des tirs des équipes pour les cinq grands championnats européens. Les analyses concernant le ratio tirs/buts des équipes nous a permis de comprendre que plusieurs équipes que nous n’attendions pas en haut des classements européens comme le Bayer Leverkusen, Stuttgart, Girone et Hoffenheim font preuve d’un fort réalisme face au but. Il sera intéressant de réitérer cette étude en fin de saison pour voir si les tendances se sont confirmées ou infirmées. Nous pourrions également réaliser des analyses approfondis sur la position des tirs et buts des équipes.